﻿namespace EFS.RC.CC.Tools.Players
{
	/// <summary>
	/// Music palyer
	/// </summary>
	internal class Audio : EFS.Common.Async.AsyncRoot
	{
		#region Composition

		// sync access
		private object syncAccess = new object();

		/// Conference participant
		private EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant = null;

		// Player conversation
		private Microsoft.Rtc.Collaboration.Conversation conversation = null;

		// Player call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall audioVideoCall = null;

		// Player
		private Microsoft.Rtc.Collaboration.AudioVideo.Player audioVideoPlayer = null;

		// File
		private Microsoft.Rtc.Collaboration.AudioVideo.WmaFileSource fileSource = null;

		// file name
		private System.String fileName = null;

		/// <summary>
		/// Status
		/// </summary>
		public enum MusicStatus { Idle, Started, Terminated }

		// Status
		private MusicStatus status = MusicStatus.Idle;

		/// <summary>
		/// Repeate
		/// </summary>
		private System.Boolean repeat = true;

		#endregion

		#region Delegates

		/// <summary>
		/// Asynk terminate 
		/// </summary>
		public EFS.Common.Async.AsyncDelegate<EFS.RC.CC.Tools.Players.Audio> AsinkTerminatedDelegate { get; private set; }

		#endregion

		#region Constructor
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="conversation"></param>
		/// <param name="remoteEndpoint"></param>
		public Audio(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant, System.String fileName, System.Boolean repeat)
		{
			// log
			EFS.EventLog.Log.LogInfo("EFS.RC.CC.Tools.Players:Audio:ctor");

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

			// delegate
			AsinkTerminatedDelegate = new EFS.Common.Async.AsyncDelegate<EFS.RC.CC.Tools.Players.Audio>(this);

			// set
			this.officeServiceSessionParticipant = officeServiceSessionParticipant;
			this.fileName = fileName;
			this.repeat = repeat;

			// register for terminated events
			officeServiceSessionParticipant.AsinkTerminatedDelegate.Register(OnOfficeServiceConferenceParticipantTerminated);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(officeServiceSessionParticipant.OfficeServiceConference.CustomerSession.ConversationGuid, "Audio:ctor", this);

			// log
			EFS.EventLog.Log.LogInfo("SupervisorContext", this);
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this object into a nice formated string
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format("EFS.RC.CC.Tools.Players.Audio [{0}]", officeServiceSessionParticipant.OfficeServiceConference.Describe());
		}

		/// <summary>
		/// File name
		/// </summary>
		public System.String FileName
		{
			get { return fileName; }
		}

		/// <summary>
		/// Call
		/// </summary>
		public Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall AudioVideoCall
		{
			get { return audioVideoCall; }
		}

		/// <summary>
		/// Begin startup
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginStartup()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartup", this);

			// start
			return BeginAsyncProcess2(Startup, OnStartupComplete);
		}

		/// <summary>
		/// Begin startup
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginShutdown()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartup", this);

			// start
			return BeginAsyncProcess2(Shutdown, OnShutdownComplete);
		}

		// Status
		public MusicStatus Status
		{
			get { return status; }
		}
		#endregion

		#region Startup

		/// <summary>
		/// Startup
		/// </summary>
		private void Startup()
		{
			// log
			EFS.EventLog.Log.LogInfo("Startup", this);

			// sync access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
			lock (syncAccess)
			{
				if (status != MusicStatus.Idle)
				{
					throw new System.InvalidOperationException("Only Idle Music can be started");
				}

				// started
				status = MusicStatus.Started;
			}

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(officeServiceSessionParticipant.OfficeServiceConference.CustomerSession.ConversationGuid, "Startup", this);

			// join
			BeginAsyncProcess2(JoinOfficeConference);
		}

		/// <summary>
		/// Join office conference
		/// </summary>
		private void JoinOfficeConference()
		{
			// log
			EFS.EventLog.Log.LogInfo("JoinOfficeConference", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(officeServiceSessionParticipant.OfficeServiceConference.CustomerSession.ConversationGuid, "JoinOfficeConference", this); 

			// get conference uri
			System.String conferenceUri = officeServiceSessionParticipant.OfficeServiceConference.ConferenceUri;

			// get end point
			Microsoft.Rtc.Collaboration.ApplicationEndpoint applicationEndpoint = officeServiceSessionParticipant.OfficeServiceConference.CustomerSession.CustomersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint;

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
			lock (syncAccess)
			{
				// check status
				if (status != MusicStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// make an uri to impersonate into the back conversation
				System.String inpersonateUri = System.String.Format("sip:{0}@{1}", System.Guid.NewGuid(), applicationEndpoint.DefaultDomain);
				System.String inpersonateDisplayName = "Music Tool";

				// log impersonation
				EFS.EventLog.Log.LogInfo(System.String.Format("Impersonate conversation {0}", inpersonateUri), this);

				// make office conversation
				conversation = new Microsoft.Rtc.Collaboration.Conversation(applicationEndpoint);
				conversation.Impersonate(inpersonateUri, null, inpersonateDisplayName);
				conversation.ApplicationContext = this;

				// conference options
				Microsoft.Rtc.Collaboration.ConferenceJoinOptions conferenceJoinOptions = new Microsoft.Rtc.Collaboration.ConferenceJoinOptions();
				conferenceJoinOptions.JoinMode = Microsoft.Rtc.Collaboration.JoinMode.TrustedParticipant;

				// establish
				conversation.ConferenceSession.BeginJoin(
					conferenceUri,
					conferenceJoinOptions,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndJoinOfficeServiceConference));
			}
		}

		/// <summary>
		/// End establish
		/// </summary>
		/// <param name="result"></param>
		private void EndJoinOfficeServiceConference(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("EndJoinOfficeServiceConference", this);

			try
			{
				// try 
				conversation.ConferenceSession.EndJoin(ar);

				// call 
				BeginAsyncProcess2(CallOfficeConference);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Call office conference
		/// </summary>
		private void CallOfficeConference()
		{
			// log
			EFS.EventLog.Log.LogInfo("CallOfficeConference", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
			lock (syncAccess)
			{
				// check status
				if (status != MusicStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// call to make audio
				audioVideoCall = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall(conversation);

				// remove from default routing
				Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallEstablishOptions audioVideoCallEstablishOptions = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallEstablishOptions();
				audioVideoCallEstablishOptions.AudioVideoMcuDialInOptions.RemoveFromDefaultRouting = true;

				// establish
				audioVideoCall.BeginEstablish(
					audioVideoCallEstablishOptions,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(OnCallEstablished));
			}
		}

		/// <summary>
		/// Call establish result
		/// </summary>
		/// <param name="result"></param>
		private void OnCallEstablished(System.IAsyncResult result)
		{
			EFS.EventLog.Log.LogInfo("OnCallEstablished", this);

			try
			{
				// check
				audioVideoCall.EndEstablish(result);

				// prepare file
				BeginAsyncProcess2(PrepareFileSource);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Prepare file source
		/// </summary>
		private void PrepareFileSource()
		{
			// log
			EFS.EventLog.Log.LogInfo("PrepareFileSource", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
			lock (syncAccess)
			{
				// check status
				if (status != MusicStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// make music source
				fileSource = new Microsoft.Rtc.Collaboration.AudioVideo.WmaFileSource(EFS.Common.Directory.MapApplicationPath(fileName));
				System.IAsyncResult ar = fileSource.BeginPrepareSource(Microsoft.Rtc.Collaboration.AudioVideo.MediaSourceOpenMode.Buffered, OnPrepareSourceCompleted, null);
			}
		}

		/// <summary>
		/// Musc loaded, start player
		/// </summary>
		/// <param name="result"></param>
		private void OnPrepareSourceCompleted(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("OnPrepareSourceCompleted", this);

			try
			{
				// check
				fileSource.EndPrepareSource(ar);

				// start
				BeginAsyncProcess2(StartAudioVideoPlayer);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Start player
		/// </summary>
		private void StartAudioVideoPlayer()
		{
			// log
			EFS.EventLog.Log.LogInfo("StartAudioVideoPlayer", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
			lock (syncAccess)
			{
				// check status
				if (status != MusicStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// Create a new Player object to pipe the music  
				audioVideoPlayer = new Microsoft.Rtc.Collaboration.AudioVideo.Player();
				audioVideoPlayer.SetSource(fileSource);
				audioVideoPlayer.SetMode(Microsoft.Rtc.Collaboration.AudioVideo.PlayerMode.Automatic);
				audioVideoPlayer.AttachFlow(audioVideoCall.Flow);
				audioVideoPlayer.Start();
				audioVideoPlayer.StateChanged += OnAudioVideoPlayerStateChanged;

				// add private audio channel to participant
				AddPrivateChannel(officeServiceSessionParticipant.ParticipantEndPoint);
			}
		}

		/// <summary>
		/// Add private channel
		/// </summary>
		private void AddPrivateChannel(Microsoft.Rtc.Collaboration.ParticipantEndpoint participantEndPoint)
		{
			EFS.EventLog.Log.LogInfo("AddPrivateChannel", this);

			// check
			EFS.Common.Verify.Reference(participantEndPoint, "participantEndPoint");

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
			lock (syncAccess)
			{
				// check status
				if (status != MusicStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// ouging routes
				System.Collections.Generic.List<Microsoft.Rtc.Collaboration.AudioVideo.OutgoingAudioRoute> outgoingAudioRoutes =
					new System.Collections.Generic.List<Microsoft.Rtc.Collaboration.AudioVideo.OutgoingAudioRoute>();

				// make outgoing route 
				Microsoft.Rtc.Collaboration.AudioVideo.OutgoingAudioRoute outgoingAudioRoute = new Microsoft.Rtc.Collaboration.AudioVideo.OutgoingAudioRoute(participantEndPoint);
				outgoingAudioRoute.Operation = Microsoft.Rtc.Collaboration.AudioVideo.RouteUpdateOperation.Add;

				// add
				outgoingAudioRoutes.Add(outgoingAudioRoute);

				// update routes
				audioVideoCall.AudioVideoMcuRouting.BeginUpdateAudioRoutes(
					outgoingAudioRoutes,
					null,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndUpdateAudioRoutes));
			}
		}

		/// <summary>
		/// End establish
		/// </summary>
		/// <param name="result"></param>
		public void EndUpdateAudioRoutes(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("EndUpdateAudioRoutes", this);

			try
			{
				// try 
				audioVideoCall.AudioVideoMcuRouting.EndUpdateAudioRoutes(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Start-up complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnStartupComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnStartupComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				EFS.EventLog.Log.LogInfo("OnStartupComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// log
				HandleExceptionSafe(ex);
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Unhandled exception
		/// </summary>
		/// <param name="ex"></param>
		protected override void OnException(System.Exception ex)
		{
			EFS.EventLog.Log.LogInfo("OnException", this);

			// check
			EFS.Common.Verify.Reference(ex, "ex");

			// log
			EFS.EventLog.Log.LogException(ex, this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(officeServiceSessionParticipant.OfficeServiceConference.CustomerSession.ConversationGuid, "OnException", ex, this);

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Customer session terminated
		/// </summary>
		/// <param name="playerHunter"></param>
		private void OnOfficeServiceConferenceParticipantTerminated(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant)
		{
			EFS.EventLog.Log.LogInfo("OnOfficeServiceConferenceTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

			// check mine
			EFS.Common.Verify.Condition(this.officeServiceSessionParticipant.Equals(officeServiceSessionParticipant), "Terminated OfficeService Conferece Participant is different");

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// State changefd
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void OnAudioVideoPlayerStateChanged(object sender, Microsoft.Rtc.Collaboration.AudioVideo.PlayerStateChangedEventArgs e)
		{
			EFS.EventLog.Log.LogInfo("OnAudioVideoPlayerStateChanged", this);

			try
			{
				// check if music ended
				if ((e.State == Microsoft.Rtc.Collaboration.AudioVideo.PlayerState.Stopped) && (status == MusicStatus.Started))
				{
					if (repeat)
					{
						// if stopped, start again
						audioVideoPlayer.Start();
					}
					else
					{
						BeginShutdown();
					}
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}
		}

		#endregion

		#region Shutdown

		/// <summary>
		/// Shut down
		/// </summary>
		public void Shutdown()
		{
			EFS.EventLog.Log.LogInfo("Shutdown", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
			lock (syncAccess)
			{
				// check already terminated
				if (MusicStatus.Terminated == status)
				{
					// already terminated
					return;
				}

				// set terinated
				status = MusicStatus.Terminated;

			}
			
			// notify terminate
			AsinkTerminatedDelegate.Start();

			// make termination
			EFS.RC.CC.Tools.Functions.Termination termination = new EFS.RC.CC.Tools.Functions.Termination(this);

			// terminate all
			termination.Stop(audioVideoPlayer);
			termination.Close(fileSource);
			termination.Terminate(conversation);
		}

		/// <summary>
		/// Shut down complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnShutdownComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnShutdownComplete", this);

			try
			{
				// check
				ap.Throw();
			}
			catch (System.Exception ex)
			{
				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		#endregion
	}
}
